import random
from typing import List, Dict, Tuple, Optional
from itertools import combinations
from collections import Counter
from .card import Card, Deck
from .hand_evaluator import evaluate_hand, PokerHand
from functools import lru_cache
import numpy as np
from concurrent.futures import ThreadPoolExecutor

class LRUCache:
    def __init__(self, capacity: int):
        self.capacity = capacity
        self.cache = {}
        self.order = []

    def get(self, key):
        if key in self.cache:
            self.order.remove(key)
            self.order.append(key)
            return self.cache[key]
        return None

    def put(self, key, value):
        if key in self.cache:
            self.order.remove(key)
        self.cache[key] = value
        self.order.append(key)
        if len(self.order) > self.capacity:
            oldest = self.order[0]
            del self.cache[oldest]
            self.order.remove(oldest)

class HandEvaluator:
    """德州扑克牌型概率计算器"""
    
    def __init__(self):
        # 创建一副完整的牌
        self.all_cards = [Card(suit, rank) 
                         for suit in Card.SUITS 
                         for rank in Card.RANKS]
        # 添加缓存
        self.probability_cache = LRUCache(10000)
        self.equity_cache = LRUCache(10000)
        self.strength_cache = LRUCache(10000)
        self.win_prob_cache = LRUCache(10000)  # 新增胜率缓存
        
        # 预计算常用值
        self.preflop_strengths = self._precompute_preflop_strengths()
        
    def _precompute_preflop_strengths(self) -> Dict[Tuple[str, str], float]:
        """预计算所有可能的翻牌前手牌强度"""
        strengths = {}
        for i, card1 in enumerate(self.all_cards):
            for card2 in self.all_cards[i+1:]:
                key = self._get_hole_cards_key(card1, card2)
                strengths[key] = self._calculate_raw_preflop_strength(card1, card2)
        return strengths
        
    def _get_hole_cards_key(self, card1: Card, card2: Card) -> Tuple[str, str]:
        """获取手牌的标准化键值"""
        cards = sorted([str(card1), str(card2)])
        return tuple(cards)
    
    def calculate_hand_probabilities(self,
                                   hole_cards: List[Card],
                                   community_cards: List[Card],
                                   num_simulations: int = 10000) -> Dict[str, float]:
        # 检查缓存
        cache_key = (
            self._get_hole_cards_key(hole_cards[0], hole_cards[1]),
            tuple(str(card) for card in community_cards),
            num_simulations
        )
        cached_result = self.probability_cache.get(cache_key)
        if cached_result is not None:
            return cached_result
            
        # 移除已知的牌
        available_cards = np.array([
            card for card in self.all_cards 
            if card not in hole_cards and card not in community_cards
        ])
        
        # 使用numpy进行向量化计算
        hand_counts = Counter()
        
        # 并行模拟
        with ThreadPoolExecutor(max_workers=4) as executor:
            batch_size = num_simulations // 4
            futures = []
            
            for _ in range(4):
                future = executor.submit(
                    self._simulate_batch,
                    hole_cards,
                    community_cards,
                    available_cards,
                    batch_size
                )
                futures.append(future)
                
            # 合并结果
            for future in futures:
                batch_counts = future.result()
                for hand_type, count in batch_counts.items():
                    hand_counts[hand_type] += count
        
        # 计算概率
        total_simulations = sum(hand_counts.values())
        probabilities = {
            hand_type: count / total_simulations 
            for hand_type, count in hand_counts.items()
        }
        
        # 缓存结果
        self.probability_cache.put(cache_key, probabilities)
        return probabilities
        
    def _simulate_batch(self,
                       hole_cards: List[Card],
                       community_cards: List[Card],
                       available_cards: np.ndarray,
                       batch_size: int) -> Counter:
        """并行模拟一批次"""
        hand_counts = Counter()
        
        for _ in range(batch_size):
            # 随机选择剩余的公共牌
            remaining_cards = 5 - len(community_cards)
            if remaining_cards > 0:
                indices = np.random.choice(
                    len(available_cards),
                    remaining_cards,
                    replace=False
                )
                simulated_cards = available_cards[indices]
                simulated_community = community_cards + list(simulated_cards)
            else:
                simulated_community = community_cards
                
            # 评估牌型
            hand = evaluate_hand(hole_cards, simulated_community)
            hand_counts[hand.hand_type] += 1
            
        return hand_counts
    
    def calculate_pot_equity(self,
                           hole_cards: List[Card],
                           community_cards: List[Card],
                           pot_size: int,
                           num_opponents: int,
                           num_simulations: int = 10000) -> float:
        # 检查缓存
        cache_key = (
            self._get_hole_cards_key(hole_cards[0], hole_cards[1]),
            tuple(str(card) for card in community_cards),
            pot_size,
            num_opponents,
            num_simulations
        )
        cached_result = self.equity_cache.get(cache_key)
        if cached_result is not None:
            return cached_result
            
        win_prob = self.calculate_win_probability(
            hole_cards, community_cards, num_opponents, num_simulations
        )
        equity = win_prob * pot_size
        
        # 缓存结果
        self.equity_cache.put(cache_key, equity)
        return equity
    
    def get_hand_strength(self,
                         hole_cards: List[Card],
                         community_cards: List[Card]) -> float:
        # 检查缓存
        cache_key = (
            self._get_hole_cards_key(hole_cards[0], hole_cards[1]),
            tuple(str(card) for card in community_cards)
        )
        cached_result = self.strength_cache.get(cache_key)
        if cached_result is not None:
            return cached_result
            
        if not community_cards:
            # 使用预计算的翻牌前强度
            strength = self.preflop_strengths[
                self._get_hole_cards_key(hole_cards[0], hole_cards[1])
            ]
        else:
            # 计算翻牌后的手牌强度
            strength = self._calculate_postflop_strength(hole_cards, community_cards)
            
        # 缓存结果
        self.strength_cache.put(cache_key, strength)
        return strength
        
    def _calculate_raw_preflop_strength(self, card1: Card, card2: Card) -> float:
        """计算原始翻牌前强度(无缓存版本)"""
        # 成对
        if card1.rank == card2.rank:
            rank_value = card1.rank_value
            return 0.5 + rank_value / (2 * len(Card.RANKS))
        
        # 同花
        suited = card1.suit == card2.suit
        
        # 计算牌的大小
        high_card = max(card1.rank_value, card2.rank_value)
        low_card = min(card1.rank_value, card2.rank_value)
        
        # 连牌
        connected = high_card - low_card == 1
        
        # 根据以上因素计算强度
        strength = (high_card / len(Card.RANKS)) * 0.5  # 高牌因素
        if suited:
            strength += 0.1
        if connected:
            strength += 0.1
        if high_card - low_card <= 3:  # 有潜力成顺子
            strength += 0.05
        
        return min(1.0, strength)
    
    def _calculate_postflop_strength(self,
                                   hole_cards: List[Card],
                                   community_cards: List[Card]) -> float:
        """计算翻牌后的手牌强度"""
        # 评估当前牌型
        current_hand = evaluate_hand(hole_cards, community_cards)
        
        # 使用较少的模拟次数计算潜力
        potential = self.calculate_hand_probabilities(
            hole_cards, community_cards, num_simulations=1000
        )
        
        # 根据当前牌型和潜力计算强度
        strength = current_hand.rank / 9.0  # 基础强度
        
        # 考虑改善的可能性
        for hand_type, prob in potential.items():
            if _get_hand_rank(hand_type) > current_hand.rank:
                strength += prob * 0.1  # 每个更好的牌型增加一些强度
        
        return min(1.0, strength)

    def calculate_win_probability(self,
                                hole_cards: List[Card],
                                community_cards: List[Card],
                                num_opponents: int = 1,
                                num_simulations: int = 10000) -> float:
        """计算胜率
        
        Args:
            hole_cards: 手牌
            community_cards: 公共牌
            num_opponents: 对手数量
            num_simulations: 模拟次数
            
        Returns:
            胜率(0-1之间的浮点数)
        """
        # 检查缓存
        cache_key = (
            self._get_hole_cards_key(hole_cards[0], hole_cards[1]),
            tuple(str(card) for card in community_cards),
            num_opponents,
            num_simulations
        )
        cached_result = self.win_prob_cache.get(cache_key)
        if cached_result is not None:
            return cached_result
            
        # 移除已知的牌
        available_cards = [
            card for card in self.all_cards 
            if card not in hole_cards and card not in community_cards
        ]
        
        wins = 0
        
        # 并行模拟
        with ThreadPoolExecutor(max_workers=4) as executor:
            batch_size = num_simulations // 4
            futures = []
            
            for _ in range(4):
                future = executor.submit(
                    self._simulate_win_probability_batch,
                    hole_cards,
                    community_cards,
                    available_cards,
                    num_opponents,
                    batch_size
                )
                futures.append(future)
                
            # 合并结果
            for future in futures:
                batch_wins = future.result()
                wins += batch_wins
        
        win_probability = wins / num_simulations
        
        # 缓存结果
        self.win_prob_cache.put(cache_key, win_probability)
        return win_probability
        
    def _simulate_win_probability_batch(self,
                                      hole_cards: List[Card],
                                      community_cards: List[Card],
                                      available_cards: List[Card],
                                      num_opponents: int,
                                      batch_size: int) -> int:
        """并行模拟一批次胜率计算"""
        batch_wins = 0
        available_cards = available_cards.copy()
        
        for _ in range(batch_size):
            # 洗牌
            random.shuffle(available_cards)
            current_idx = 0
            
            # 为对手发牌
            opponent_hands = []
            for _ in range(num_opponents):
                opponent_hand = [
                    available_cards[current_idx],
                    available_cards[current_idx + 1]
                ]
                opponent_hands.append(opponent_hand)
                current_idx += 2
            
            # 补齐公共牌
            remaining_community = 5 - len(community_cards)
            simulated_community = community_cards + [
                available_cards[i] 
                for i in range(current_idx, current_idx + remaining_community)
            ]
            
            # 评估玩家的牌
            player_hand = evaluate_hand(hole_cards, simulated_community)
            
            # 评估对手的牌
            is_win = True
            for opponent_hole_cards in opponent_hands:
                opponent_hand = evaluate_hand(opponent_hole_cards, simulated_community)
                if opponent_hand > player_hand:
                    is_win = False
                    break
            
            if is_win:
                batch_wins += 1
                
        return batch_wins

def _get_hand_rank(hand_type: str) -> int:
    """获取牌型等级"""
    ranks = {
        "皇家同花顺": 9,
        "同花顺": 8,
        "四条": 7,
        "葫芦": 6,
        "同花": 5,
        "顺子": 4,
        "三条": 3,
        "两对": 2,
        "一对": 1,
        "高牌": 0
    }
    return ranks[hand_type] 